package com.yjd.comm.borrow.util;

import com.yjd.comm.borrow.dto.ReturnFeeDto;
import com.yjd.comm.borrow.dto.ReturnFeeDto.EveryReturnFeeDto;
import com.yjd.comm.util.ArithUtil;
import com.yjd.comm.util.FrameUtil;
import com.yjd.comm.vo.Constant.CalcModeEnum;

import java.math.BigDecimal;
import java.util.ArrayList;

/**
 * <b>description</b>：费用计算 <br>
 * <b>time</b>：2014-8-22 下午7:14:54 <br>
 * <b>author</b>： ready likun_557@163.com
 */
public class FeeCalcUtil {

	/**
	 * 费用保留位数
	 */
	public static final int PREC = 2;

	/**
	 * 默认所有的资金保留两位小数
	 * 
	 * @param d
	 * @return
	 */
	public static Double round(Double d) {
		if (d == null) {
			return 0D;
		}
		BigDecimal bg = new BigDecimal(Double.toString(d));
		return bg.setScale(PREC, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

	/**
	 * 计算费用
	 * 
	 * @param calcModeEnum
	 * @param capital
	 * @param apr
	 * @param m
	 * @return
	 */
	public static ReturnFeeDto count(CalcModeEnum calcModeEnum, double capital,
			double apr, int m, long curTime) {
		if (calcModeEnum == CalcModeEnum.SAVE_CAPITAL_INTEREST) {
			return countA(capital, apr, m, curTime);
		} else if (calcModeEnum == CalcModeEnum.ON_MONTH_INTEREST) {
			return countB(capital, apr, m, curTime);
		}
		return null;
	}

	/**
	 * 等额本息计算
	 * 
	 * @param capital
	 *            本金
	 * @param apr
	 *            年利率，如10表示10%
	 * @param m
	 *            还款次数
	 * @return
	 */
	public static ReturnFeeDto countA(double capital, double apr, int m,
			long curTime) {
		ReturnFeeDto feeDto = new ReturnFeeDto();
		// 月利率
		double apr_m = ArithUtil.divs(apr, 100, 12);
		// 月利率的m次方
		double $_li = ArithUtil.pow(ArithUtil.add(1D, apr_m), m);
		// 月供
		double price = FeeCalcUtil.round(ArithUtil.div(
				ArithUtil.muls(capital, apr_m, $_li),
				ArithUtil.sub($_li, 1d)));
		// 总共
		double reprice = FeeCalcUtil.round(ArithUtil.div(
				ArithUtil.muls(capital, m, apr_m, $_li),
				ArithUtil.sub($_li, 1d)));
		double reprice_ = ArithUtil.mul(price, m);
		double error = (reprice == reprice_) ? 0 : FeeCalcUtil.round(ArithUtil
				.sub(reprice, reprice_));
		// 总计部分
		feeDto.setReprice(reprice);
		feeDto.setCapital(capital);
		feeDto.setInterest(FeeCalcUtil.round(ArithUtil.sub(feeDto.getReprice(),
				feeDto.getCapital())));
		feeDto.setPrice(price);

		double totalReprice = 0D;
		for (int i = 0; i < m; i++) {
			double interest = 0;
			if (i == 0) {
				interest = FeeCalcUtil.round(ArithUtil.muls(
						feeDto.getCapital(), apr_m));
			} else {
				double _lu = ArithUtil.pow((1 + apr_m), i);
				interest = FeeCalcUtil.round(ArithUtil.add(ArithUtil.mul(
						ArithUtil.sub(
								ArithUtil.mul(feeDto.getCapital(), apr_m),
								feeDto.getPrice()), _lu), feeDto.getPrice()));
			}
			int n = i + 1;
			// 等额本息有个误差，最后一个月的利息补上误差
			if (n == m) {
				interest = ArithUtil.add(interest, error);
			}
			EveryReturnFeeDto ed = new EveryReturnFeeDto();
			ed.setPrice((n == m) ? ArithUtil.add(feeDto.getPrice(), error)
					: feeDto.getPrice());
			ed.setInterest(interest);
			ed.setCapital(FeeCalcUtil.round(ArithUtil.sub(ed.getPrice(),
					interest)));
			ed.setReday(BorrowUtil.countRepayDay(n, curTime));
			if (feeDto.getData() == null) {
				feeDto.setData(new ArrayList<EveryReturnFeeDto>());
			}
			feeDto.getData().add(ed);
			
			totalReprice = ArithUtil.adds(totalReprice,
					ed.getInterest(), ed.getCapital());
		}
		return feeDto;
	}

	/**
	 * 每月付息 到期还本
	 * 
	 * @param capital
	 *            本金
	 * @param apr
	 *            年利率，如10表示10%
	 * @param m
	 *            还款次数
	 * @return
	 */
	public static ReturnFeeDto countB(double capital, double apr, int m,
			long curTime) {
		ReturnFeeDto feeDto = new ReturnFeeDto();
		feeDto.setReprice(0D);
		// 月供利息
		double price = FeeCalcUtil.round(ArithUtil.div(
				ArithUtil.muls(capital, apr, 0.01D), 12));
		// 组装
		feeDto.setCapital(capital);
		feeDto.setInterest(FeeCalcUtil.round(ArithUtil.muls(price, m)));
		feeDto.setPrice(price);
		for (int i = 1; i <= m; i++) {
			EveryReturnFeeDto ed = new EveryReturnFeeDto();
			ed.setInterest(FeeCalcUtil.round(feeDto.getPrice()));
			ed.setCapital(FeeCalcUtil.round(i == m ? capital : 0));
			ed.setPrice(FeeCalcUtil.round(ArithUtil.add(ed.getInterest(),
					ed.getCapital())));
			ed.setReday(BorrowUtil.countRepayDay(i, curTime));
			if (feeDto.getData() == null) {
				feeDto.setData(new ArrayList<EveryReturnFeeDto>());
			}
			feeDto.getData().add(ed);
			feeDto.setReprice(FeeCalcUtil.round(ArithUtil.add(
					feeDto.getReprice(), ed.getPrice())));
		}
		return feeDto;
	}
	
	/**
	 * 等额本金
	 * 
	 * @param capital
	 *            本金
	 * @param apr
	 *            年利率，如10表示10%
	 * @param m
	 *            还款次数
	 * @return
	 */
	public static ReturnFeeDto countC(double capital, double apr, int m,
			long curTime) {
		ReturnFeeDto feeDto = new ReturnFeeDto();
		feeDto.setReprice(0D);
		// 每月本金
		double every_capital = FeeCalcUtil.round(ArithUtil.div(capital, m));
		// 月利率
		double every_month_apr = ArithUtil.divs(apr, 1200);
		// 还款本金
		feeDto.setCapital(capital);
		// 还款总利息=（还款月数+1）*贷款额*月利率/2
		feeDto.setInterest(FeeCalcUtil.round(ArithUtil.div(
				ArithUtil.muls(ArithUtil.add(m, 1), capital, every_month_apr),
				2)));
		// 还款总额 = (还款月数+1)*贷款额*月利率/2+贷款额
		feeDto.setReprice(FeeCalcUtil.round(ArithUtil.add(feeDto.getInterest(),
				capital)));
		// 还款本金
		feeDto.setCapital(capital);
		// 已还本金
		double return_capical = 0;
		// 还款总本金
		double total_capital = 0;
		// 还款总利息
		double total_interest = 0;
		for (int i = 1; i <= m; i++) {
			EveryReturnFeeDto ed = new EveryReturnFeeDto();
			ed.setCapital(every_capital);
			// 月利息=(本金-累计已还本金)×月利率
			ed.setInterest(FeeCalcUtil.round(ArithUtil.mul(
					(ArithUtil.sub(capital, return_capical)), every_month_apr)));
			ed.setPrice(ArithUtil.add(ed.getInterest(), ed.getCapital()));
			ed.setReday(BorrowUtil.countRepayDay(i, curTime));
			if (feeDto.getData() == null) {
				feeDto.setData(new ArrayList<EveryReturnFeeDto>());
			}
			feeDto.getData().add(ed);
			total_capital = ArithUtil.add(total_capital, ed.getCapital());
			total_interest = ArithUtil.add(total_interest, ed.getInterest());
			if (i == m) {
				// 补最后一笔误差
				ed.setCapital(ArithUtil.add(feeDto.getCapital(),
						ArithUtil.sub(feeDto.getCapital(), total_capital)));
				ed.setInterest(ArithUtil.add(feeDto.getInterest(),
						ArithUtil.sub(feeDto.getInterest(), total_interest)));
			}
			return_capical += every_capital;
		}
		return feeDto;
	}

	public static void main(String[] args) {
		/*System.out.println(FrameUtil.json(FeeCalcUtil.count(CalcModeEnum.SAVE_CAPITAL_INTEREST, 100d, 10d, 10,
				FrameUtil.getTime(null))));
		System.out.println(FrameUtil.json(FeeCalcUtil.count(CalcModeEnum.SAVE_CAPITAL_INTEREST, 200d, 10d, 10,
				FrameUtil.getTime(null))));*/
		System.out.println(FrameUtil.json(FeeCalcUtil.count(CalcModeEnum.SAVE_CAPITAL_INTEREST, 10000d, 18, 12,
				FrameUtil.getTime(null)),true));
		/*System.out.println(FrameUtil.json(FeeCalcUtil.countC(1000000d, 6, 240,
				FrameUtil.getTime(null)),true));*/
	}
}
